// vim:tabstop=4 shiftwidth=4 syntax=objc
//xxx
#import "GPS.h"
#import "SharedPrefs.h"
#import "NGUtility.h"
#import "NGLocation.h"
#import "NGLocationUpdatedDelegate.h"
#import "NGWeb.h"
#import "RotatingNGLocation.h"
#import "NGAppDelegate.h"


#define _KEY_PATH			@"last_pos"
#define _WATCHDOG_SECONDS	(60 * 60)

@interface GPS ()
- (void)ping_watchdog;
- (void)stop_watchdog;
- (void)restart;
@end

@implementation GPS

static GPS *g_;

//yyy
@synthesize last_pos = last_pos_;
@synthesize gps_is_on = gps_is_on_;
@synthesize accuracy = accuracy_;
@synthesize locationUpdatedDelegate;
@synthesize LAST_N_LOCATIONS;

/** Returns the pointer to the singleton GPS class.
 * The class will be constructed if necessary.
 */
+ (GPS*)get
{
	if (!g_) {
		g_ = [GPS new];
		const BOOL gps_is_on = [SharedPrefs useGPS];
        [g_ setGps_is_on:gps_is_on];
        [g_ setLAST_N_LOCATIONS: [[RotatingNGLocation alloc] initWithCapacity:LAST_N_LOCATIONS_SIZE]];
        //[DB open_database];
		//if (gps_is_on)
			//[g_ start];
	}
    
    NSLog(@"GET");
    NSLog(@"Konum sayisi %d\n", [SharedPrefs getLocationSize]);
    NSLog(@"Zaman araligi %lf\n", [SharedPrefs getTimeInterval]);
    NSLog(@"Mesafe %d\n", [SharedPrefs getDeltaDistance]);
    NSLog(@"Gonderim sikligi %d\n", [SharedPrefs getSendFrequency]);

	return g_;
}

+ (RotatingNGLocation *) getLastNLocations {
    return g_.LAST_N_LOCATIONS;
}

/** Converts a coordinate from degrees to decimal minute second format.
 * Specify with the latitude bool if you are converting the latitude
 * part of the coordinates, which has a different letter.
 *
 * \return Returns the string with the formated value as
 * Ddeg Mmin Ssec X, where X is a letter.
 */
+ (NSString*)degrees_to_dms:(CLLocationDegrees)value latitude:(BOOL)latitude
{
	const int degrees = fabsl(value);
	const double min_rest = (fabs(value) - degrees) * 60.0;
	const int minutes = min_rest;
	const double seconds = (min_rest - minutes) * 60.0;
	char letter = 0;
	if (latitude) {
		if (value > 0)
			letter = 'N';
		else if (value < 0)
			letter = 'S';
	} else {
		if (value > 0)
			letter = 'E';
		else if (value < 0)
			letter = 'W';
	}
	if (letter)
		return [NSString stringWithFormat:@"%ddeg %dmin %0.2fsec %c",
			degrees, minutes, seconds, letter];
	else
		return [NSString stringWithFormat:@"%ddeg %dmin %0.2fsec",
			degrees, minutes, seconds];
}

#pragma mark - initialization

- (id)init {
	if (!(self = [super init]))
		return nil;
    return [self init:self];
}

/** Initialises the GPS class.
 */
- (id)init:(id)locationManagerDelegate
{
	if (!(self = [super init]))
		return nil;

#ifdef FAKE_CORE_LOCATION
    manager_ = [[FTLocationSimulator alloc] init];
#else
    manager_ = [[CLLocationManager alloc] init];
#endif  
	
	if (!manager_) {
		//LOG(@"Couldn't instantiate CLLocationManager!");
		return nil;
	}

    NSLog(@"locationManagerDelegate @ %d\n", __LINE__);
    
	// Set no filter and try to get the best accuracy possible.
	accuracy_ = HIGH_ACCURACY;
	manager_.distanceFilter = [SharedPrefs getTimeInterval];
	manager_.desiredAccuracy = [SharedPrefs getDeltaDistance];
	manager_.delegate = locationManagerDelegate;

	return self;
}

- (void)dealloc
{
	[self stop];
	[manager_ release];
	[super dealloc];
}

/** Write property to serialize value for later usage.
 * Setting the variable also saves it to the user's defaults,
 * allowing the value to be restored in case of crash or device without
 * background tracking support.
 */
- (void)setGps_is_on:(BOOL)value
{
	gps_is_on_ = value;
    //[SharedPrefs setUseGPS:value];
}

- (void)setLAST_N_LOCATIONS:(RotatingNGLocation *)rLocations;
{
	LAST_N_LOCATIONS = rLocations;
}


/** 
 * Starts the GPS tracking.
 * Returns false if the location services are not available.
 * If start is invoked more than once it will register two location update so that
 * more than one location update event will be trigger, CAUTION
 * Maybe a control flag can be put here //TODO
 */
- (bool)start
{
    
	//if (manager_.locationServicesEnabled) {
    bool locationServicesEnabled = FALSE;
#ifdef FAKE_CORE_LOCATION
    locationServicesEnabled = YES; //[FTLocationSimulator locationServicesEnabled];
#else
    locationServicesEnabled = [CLLocationManager locationServicesEnabled];
#endif       
    
    if (locationServicesEnabled) {
		if (!self.gps_is_on && !nolog_) {
			//[[DB get] log:@"Starting to update location"];
        }
		//[self ping_watchdog];

		[manager_ startUpdatingLocation];
		self.gps_is_on = YES;
        NSLog(@"GPS.start -> GPS IS ON");
		return true;
	} else {
		self.gps_is_on = NO;
        NSLog(@"GPS.start -> GPS IS OFF");       
		return false;
	}
    NSLog(@"GPS.m is working");
}

/** Stops the GPS tracking.
 * You can call this function anytime, doesn't really fail.
 */
- (void)stop
{
	if (self.gps_is_on && !nolog_) {
		//[[DB get] log:@"Stopping to update location"];
	}
    [self stop_watchdog];
	self.gps_is_on = NO;
	[manager_ stopUpdatingLocation];
}

/** Returns the string used by add_watcher: and removeObserver:.
 */
+ (NSString*)key_path
{
	return _KEY_PATH;
}

/** Registers an observer for changes to last_pos.
 * Observers will monitor the key_path value.
 */
- (void)add_watcher:(id)watcher
{
	[self addObserver:watcher forKeyPath:_KEY_PATH
		options:NSKeyValueObservingOptionNew context:nil];
}

/** Removes an observer for changes to last_pos.
 */
- (void)remove_watcher:(id)watcher
{
	[self removeObserver:watcher forKeyPath:_KEY_PATH];
}

/** Changes the desired accuracy of the GPS readings.
 * If the GPS is on, it will be reset just in case. Provide a reason
 * for the change or nil if you don't want to log the change.
 */
- (void)set_accuracy:(ACCURACY)accuracy reason:(NSString*)reason
{
	if (accuracy_ == accuracy)
		return;

	accuracy_ = accuracy;

    NSLog(@"Konum sayisi %d\n", [SharedPrefs getLocationSize]);
    NSLog(@"Zaman araligi %f\n", (double)[SharedPrefs getTimeInterval]);
    NSLog(@"Mesafe %d\n", [SharedPrefs getDeltaDistance]);
    NSLog(@"Gonderim sikligi %d\n", [SharedPrefs getSendFrequency]);
    
    
    manager_.desiredAccuracy = [SharedPrefs getDeltaDistance];
    manager_.distanceFilter = [SharedPrefs getTimeInterval];

	if (self.gps_is_on) {
		if (reason.length > 0) {
			//[[DB get] log:[NSString stringWithFormat:@"%@ Reason: %@", message, reason]];
		}
        else {
			//[[DB get] log:message];
        }
		nolog_ = YES;
		[self stop];
		[self start];
		nolog_ = NO;
	}
}

#pragma mark CLLocationManagerDelegate

/** Something bad happened retrieving the location. What?
 * We ignore location errors only. Rest are logged.
 */
- (void)locationManager:(CLLocationManager *)manager
	didFailWithError:(NSError *)error
{
	if ([error code] == kCLErrorLocationUnknown)
		return;

    NSLog(@"%@", [NSString stringWithFormat:@"location error: %@", error]);
	//[[DB get] log:[NSString stringWithFormat:@"location error: %@", error]];
}

/** Receives a location update.
 * This generates the correct KVO messages to notify observers.
 * Also resets the watchdog. Repeated locations based on timestamp
 * will be discarded.
 */
- (void)locationManager:(CLLocationManager*)manager
		didUpdateToLocation:(CLLocation*)newLocation
		fromLocation:(CLLocation*)oldLocation
{
    NSLog(@"location Manager @ %d\n", __LINE__);

    if (NO) { //GPS metodu şimdilik kaldır, ileride açmak gerekecek
        if (newLocation.horizontalAccuracy < 0) {
            //DLOG(@"Bad returned accuracy, ignoring update.");
            return;
        }
        
        if (self.last_pos &&
			[self.last_pos.timestamp isEqualToDate:newLocation.timestamp]) {
            //DLOG(@"Discarding repeated location %@", [newLocation description]);
            return;
        }
        
        //DLOG(@"Updating to %@", [new_location description]);
        
        // Keep the new location for map showing.
        [self willChangeValueForKey:_KEY_PATH];
        [newLocation retain];
        [last_pos_ release];
        last_pos_ = newLocation;
        [self didChangeValueForKey:_KEY_PATH];
        
        [self ping_watchdog];        
    }
    

    [NGUtility setBadgeNumberForBackground];
    
    //**************
    //Console Log için, ve sunucuya göndermek için
    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
    if (abs(howRecent) < 15.0) {
        NSLog(@"latitude %+.6f, longitude %+.6f\n",
              newLocation.coordinate.latitude,
              newLocation.coordinate.longitude);
        
        NGLocation *ngLocation = [NGLocation initWithCLLocation:newLocation];
        [ngLocation calculateSpeedAndStatus:LAST_N_LOCATIONS];
        [LAST_N_LOCATIONS insertElement:ngLocation];
        
        BOOL sendToServer = [SharedPrefs isSendToServer];
        if (sendToServer) {
            [self sendLocationToServer:ngLocation];
        }
        NSLog(@"GPS -> SEND TO SERVER: %@\n-------------------\n", (sendToServer ? @"YES" : @"NO"));
    }
    //**************
}

- (void)sendLocationToServer:(NGLocation *)location {
    NSLog(@"GPS Sending to server.....");
    SetLocationRequest *request = [[SetLocationRequest alloc] initWithLocation:location];
    
    request.locationUpdatedDelegate = self.locationUpdatedDelegate;
    
    [WebServisCaller callWebService:request target:request];
}

#pragma mark Watchdog

/** Starts or refreshes the timer used for the GPS watchdog.
 * Sometimes if you loose network signal the GPS will stop updating
 * values even though the hardware may well be getting them. The
 * watchdog will set a time and force a stop/start if there were no
 * recent updates received.
 *
 * You have to call this function every time you start to watch
 * updates and every time you receive one, so the watchdog timer is
 * reset.
 *
 * Miss Merge: I'm walking on sunshine, woo hoo!
 */
- (void)ping_watchdog
{
	if (zasca_)
		[zasca_ invalidate];

	zasca_ = [NSTimer scheduledTimerWithTimeInterval:_WATCHDOG_SECONDS
		target:self selector:@selector(zasca) userInfo:nil repeats:NO];
}

/** Stops the watchdog, if it is on. Otherwise does nothing.
 */
- (void)stop_watchdog
{
	if (zasca_)
		[zasca_ invalidate];
	zasca_ = nil;
}

/** Handles the stop/start of the GPS.
 * Note that the stop/start will automatically reschedule the watchdog.
 */
- (void)restart
{
	//[[DB get] log:@"Watchdog timer kicking in due to inactivity."];
	nolog_ = YES;
	[self stop];
	[self start];
	nolog_ = NO;
}

@end
